/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.testgen;
import org.junit.ComparisonFailure;
import org.junit.runner.RunWith;
import org.springsource.loaded.test.infra.IResult;
import org.springsource.loaded.test.infra.Result;
import org.springsource.loaded.test.infra.ResultException;
/**
* This class is intended to be subclassed to create 'generated' tests. It needs to be run with the
* {@link ExploreAllChoicesRunner} test runner, using the {@link RunWith} annotation.
* <p>
* To create a generative test two things come together:
*
* <ul>
* <li>A mechanism to create different test configurations based on 'random' choices. These random choices are made by
* the test's 'setup' method calling the provided 'choice' methods.
*
* <li>A mechanism to run the same test twice in two different execution contexts. It is the responsibility of the test
* subclass to setup the appropriate execution context. See {@link GenerativeSpringLoadedTest} for an example.
* </ul>
*
* The test runner is responsible for injecting implementations of the IChoiceGenerator interface.
* <p>
* On a first run, the test runner will provide a 'recording' choice generator. The test is run multiple times until all
* possible choices have been explored. For each test run the choices are recorded together with the observed test
* result for those choices. This is used to populate the test tree.
* <p>
* Then the tests are run again replaying the recorded choices. The result is compared with the result from the first
* run. The test fails if the results are not equal (using whatever implementation of equals is provided by the result
* objects.
*
* @author kdvolder
*/
public abstract class GenerativeTest {
/**
* Injected by the test runner. Use the ChoiceGenerator to implement some logic to choose test parameters. Either in
* your setup method or your actual test method.
*/
public IChoiceGenerator choiceGenerator = null;
/**
* This field is set by the test runner to indicate whether the test is currently in 'generative' mode, or
* 'replay/verify' mode. This flag is mostly intended for the setup method so it can setup an appropriate execution
* context.
*/
public boolean generative;
/**
* This method should setup the test, using the provided choice generator to construct/choose a test configuration.
*/
public void setup() throws Exception, RejectedChoice {
}
public void teardown() throws Exception {
}
/**
* There should be only one test method in this type of test, this is it!
* <p>
* The test method will be run twice by the runner, once in a 'generative' mode and once in 'verifying' mode.
* <p>
* In generative mode, it is ok to throw RejectedChoice exception, this will cause the test to be ignored. The test
* method itself shouldn't need to know what mode it is running in. The test runner should inject the necessary
* context dependencies for the code to be identical in both cases.
* <p>
* The only obligation the test method has is to ensure that, given a deterministic set of choices is made by the
* injected ChoiceGenerator, the test method's behavior should also be deterministic.
*
* @throws ResultExeption if the test produces an expected exception as result.
* @throws RejectedChoice (in generative mode only) if the generated test should be ignored.
* @throws Exception any other exception should be treated as an unexpected error and make the test fail.
* @return Result encapsulating the expected result of the test.
*/
public abstract Result test() throws ResultException, Exception;
/**
* @return Use choice generator to pick an element from a bunch of Strings
* @throws RejectedChoice
*/
protected <T> T choice(T... options) throws RejectedChoice {
return options[choice(options.length)];
}
/**
* @return number in range 0 (inclusive) to 'hi' (exclusive)
* @throws RejectedChoice if 'hi' is negative
*/
protected int choice(int hi) throws RejectedChoice {
return choice(0, hi);
}
/**
* @return number in range 'lo' (inclusive) to 'hi' (exclusive)
* @throws RejectedChoice if 'lo' >= 'hi'
*/
protected int choice(int lo, int hi) throws RejectedChoice {
if (lo >= hi) {
throw new RejectedChoice(); //Nothing to choose from
}
if (hi - lo == 1) {
//only one choice
return lo;
}
//Use kind of 'binary search' for efficient choice making
if (choice())
return choice(lo, (lo + hi) / 2);
else
return choice((lo + hi) / 2, hi);
}
protected boolean choice() {
boolean b = choiceGenerator.nextBoolean();
return b;
}
/**
* Override this and make it return something other than null to create a nicer name in the JUnit runner view.
* Beware that this name must be unique or the Eclipse JUnit runner view will get confused displaying the results
* (though tests should still run ok).
* <p>
* Typically, you should override this to return a string that describes the values for the configuration parameter
* values of the test instance.
* <p>
* If not overridden, the choices 'bitString' will be displayed. This is unique, but not very informative.
*
* @return A String uniquely identifying the test or null.
*/
public String getConfigDescription() {
return null;
}
/**
* This method is called by the test runner to compare predicted results against actual results.
* <p>
* Override this method to customise how you want these compared.
*
* @param expected Result from the 'generative' test run.
* @param actual Result from the actual test run.
*/
final protected void assertEqualIResults(IResult expected, IResult actual) {
if (expected.equals(actual)) {
return;
}
if (expected.getClass() != actual.getClass()) {
//One is an Exception and the other one isn't. these's no way these should
//ever be treated as equivalent!
throw new ComparisonFailure(null, expected.toString(), actual.toString());
}
if (expected instanceof Result) {
assertEqualResults((Result) expected, (Result) actual);
}
else if (expected instanceof ResultException) {
assertEqualExceptions((ResultException) expected, (ResultException) actual);
}
else {
//I don't what it is?? There are only two implementations of the interface
throw new ComparisonFailure(null, expected.toString(), actual.toString());
}
}
/**
* This method gets called to compare two ResultExceptions, but only when the standard equals method returned false.
* Subclasses may override this to relax the equality check.
*/
protected void assertEqualExceptions(ResultException expected, ResultException actual) {
throw new ComparisonFailure(null, expected.toString(), actual.toString());
}
/**
* This method gets called to compare two Results, but only when the standard equals method returned false.
* Subclasses may override this to relax the equality check.
*/
protected void assertEqualResults(Result expected, Result actual) {
throw new ComparisonFailure(null, expected.toString(), actual.toString());
}
}